package network

import (
	"context"
	"crypto/rand"
	"fmt"
	log "github.com/corgi-kx/logcustom"
	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p-core/crypto"
	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/peer"
	//"github.com/libp2p/go-libp2p-core/protocol"
	"github.com/libp2p/go-libp2p-core/routing"
	kaddht "github.com/libp2p/go-libp2p-kad-dht"
	mplex "github.com/libp2p/go-libp2p-mplex"
	pubsub "github.com/libp2p/go-libp2p-pubsub"
	secio "github.com/libp2p/go-libp2p-secio"
	yamux "github.com/libp2p/go-libp2p-yamux"
	"github.com/libp2p/go-libp2p/p2p/discovery"
	"github.com/libp2p/go-tcp-transport"
	"github.com/multiformats/go-multiaddr"
	"os"
	"os/signal"
	"syscall"
	"time"
)

//在P2P网络中已发现的节点池
//key:节点ID  value:节点详细信息
var peerPool = make(map[string]peer.AddrInfo)
var ctx = context.Background()
var send = Send{}

//Websocket推送
var wsend = WebsocketSend{}

//gossip网络
var gossip = pubsub.PubSub{}

//gossip主题
const pubsubTopic = "/libp2p/user_blockchain/1.0.0"

//启动本地节点
func StartNode(clier Clier) {
	log.Infof("[*] 监听IP地址: %s 端口号: %s", ListenHost, ListenPort)

	//传输层接口 TCP
	transports := libp2p.ChainOptions(
		libp2p.Transport(tcp.NewTCPTransport),
	)
	//多路复用
	muxers := libp2p.ChainOptions(
		libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport),
		libp2p.Muxer("/mplex/6.7.0", mplex.DefaultTransport),
	)

	//安全传输
	security := libp2p.Security(secio.ID, secio.New)

	//Kademila路由
	var dht *kaddht.IpfsDHT
	newDHT := func(h host.Host) (routing.PeerRouting, error) {
		var err error
		dht, err = kaddht.New(ctx, h)
		return dht, err
	}
	routing_ := libp2p.Routing(newDHT)

	r := rand.Reader

	// 为本地节点创建RSA密钥对
	prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
	if err != nil {
		log.Panic(err)
	}

	// 创建本地节点地址信息
	sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%s", ListenHost, ListenPort))

	//传入地址信息，RSA密钥对信息，生成libp2p本地host信息
	host, err := libp2p.New(
		ctx,
		transports,
		//监听地址
		libp2p.ListenAddrs(sourceMultiAddr),
		libp2p.Identity(prvKey),
		muxers,
		security,
		routing_,
		libp2p.NATPortMap(),
		libp2p.DefaultEnableRelay,
		libp2p.DefaultPeerstore,
	)

	if err != nil {
		log.Panic(err)
	}

	//将本地主机信息写入全局变量
	localHost = host

	//本地P2P节点地址详细信息写入全局变量
	localAddr = fmt.Sprintf("/ip4/%s/tcp/%s/p2p/%s", ListenHost, ListenPort, host.ID().Pretty())
	log.Infof("[*] 你的P2P地址信息: %s", localAddr)

	//启用gossip
	ps, err := pubsub.NewGossipSub(ctx, host)
	if err != nil {
		panic(err)
	}
	//将生成的gossip网络赋值到全局变量
	gossip = *ps
	sub, err := ps.Subscribe(pubsubTopic)
	if err != nil {
		panic(err)
	}
	// TODO: Modify this handler to use the protobufs defined in this folder
	//流处理函数(接收到p2p节点广播信息的处理函数)
	go pubsubHandler(ctx, sub)

	//连接bootstrap节点
	fmt.Printf("addr: %s\n", host.ID())

	for _, addr := range host.Addrs() {
		fmt.Println("Listening on", addr)
	}
	targetAddr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%s/p2p/%s", BootstrapHost, BootstrapPort, BootstrapAddr))
	if err != nil {
		panic(err)
	}

	targetInfo, err := peer.AddrInfoFromP2pAddr(targetAddr)
	if err != nil {
		panic(err)
	}

	err = host.Connect(ctx, *targetInfo)
	for err != nil {
		fmt.Println(err)
		fmt.Println("尝试重新连接bootstrap节点......")
		err = host.Connect(ctx, *targetInfo)
		time.Sleep(1 * time.Second)
	}

	fmt.Println("Connected to", targetInfo.ID)

	//局域网主机查找技术
	mdns, err := discovery.NewMdnsService(ctx, host, time.Second*10, RendezvousString)
	if err != nil {
		panic(err)
	}
	mdns.RegisterNotifee(&mdnsNotifee{h: host, ctx: ctx})

	//路由bootstrap
	err = dht.Bootstrap(ctx)
	if err != nil {
		panic(err)
	}

	//启动程序的命令行输入环境
	//go clier.ReceiveCMD()
	fmt.Println("本地用户网络节点已启动,详细信息请查看log日志!")
	signalHandle()
}

//启动本地节点
func StartNodeMobile(clier Clier) {
	log.Infof("[*] 监听IP地址: %s 端口号: %s", ListenHost, ListenPort)

	//传输层接口 TCP
	transports := libp2p.ChainOptions(
		libp2p.Transport(tcp.NewTCPTransport),
	)
	//多路复用
	muxers := libp2p.ChainOptions(
		libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport),
		libp2p.Muxer("/mplex/6.7.0", mplex.DefaultTransport),
	)
	//安全传输
	security := libp2p.Security(secio.ID, secio.New)

	//Kademila路由
	var dht *kaddht.IpfsDHT
	newDHT := func(h host.Host) (routing.PeerRouting, error) {
		var err error
		dht, err = kaddht.New(ctx, h)
		return dht, err
	}
	routing_ := libp2p.Routing(newDHT)

	r := rand.Reader
	// 为本地节点创建RSA密钥对
	prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
	if err != nil {
		log.Panic(err)
	}
	// 创建本地节点地址信息
	sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%s", ListenHost, ListenPort))
	//传入地址信息，RSA密钥对信息，生成libp2p本地host信息
	host, err := libp2p.New(
		ctx,
		transports,
		//监听地址
		libp2p.ListenAddrs(sourceMultiAddr),
		libp2p.Identity(prvKey),
		muxers,
		security,
		routing_,
		libp2p.NATPortMap(),
		libp2p.DefaultEnableRelay,
		libp2p.DefaultPeerstore,
	)
	if err != nil {
		log.Panic(err)
	}

	//写入全局变量本地主机信息
	localHost = host
	//写入全局变量本地P2P节点地址详细信息
	localAddr = fmt.Sprintf("/ip4/%s/tcp/%s/p2p/%s", ListenHost, ListenPort, host.ID().Pretty())
	log.Infof("[*] 你的P2P地址信息: %s", localAddr)

	//启用gossip 代替上面的
	ps, err := pubsub.NewGossipSub(ctx, host)
	if err != nil {
		panic(err)
	}
	gossip = *ps
	sub, err := ps.Subscribe(pubsubTopic)
	if err != nil {
		panic(err)
	}
	// TODO: Modify this handler to use the protobufs defined in this folder
	//p2p接收信息的流处理函数
	go pubsubHandler(ctx, sub)

	//连接bootstrap节点
	fmt.Printf("addr: %s\n", host.ID())
	for _, addr := range host.Addrs() {
		fmt.Println("Listening on", addr)
	}
	targetAddr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%s/p2p/%s", BootstrapHost, BootstrapPort, BootstrapAddr))
	if err != nil {
		panic(err)
	}

	targetInfo, err := peer.AddrInfoFromP2pAddr(targetAddr)
	if err != nil {
		panic(err)
	}

	err = host.Connect(ctx, *targetInfo)
	for err != nil {
		fmt.Println("尝试重新连接bootstrap节点......")
		err = host.Connect(ctx, *targetInfo)
		time.Sleep(1 * time.Second)
	}

	fmt.Println("Connected to", targetInfo.ID)

	//局域网主机查找技术
	mdns, err := discovery.NewMdnsService(ctx, host, time.Second*10, "")
	if err != nil {
		panic(err)
	}
	mdns.RegisterNotifee(&mdnsNotifee{h: host, ctx: ctx})

	//路由bootstrap
	err = dht.Bootstrap(ctx)
	if err != nil {
		panic(err)
	}

	fmt.Println("本地用户网络节点已启动,详细信息请查看log日志!")

	signalHandle()
}

//节点退出信号处理
func signalHandle() {
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	<-sigs
	send.SendSignOutToPeers()
	fmt.Println("本地节点已退出")
	time.Sleep(time.Second)
	os.Exit(0)
}

type mdnsNotifee struct {
	h   host.Host
	ctx context.Context
}

func (m *mdnsNotifee) HandlePeerFound(pi peer.AddrInfo) {
	_ = m.h.Connect(m.ctx, pi)
}
